開始前的謎之聲:本來想要每天標題都打得很像愛情故事的,殊不知第三天開始就沒梗了,之後的標題都會以正經的方式呈現。
ASP.NET Core 大量使用依賴注入(DI),落實了在類別的相依性之間達成控制反轉(IoC)的技術。
先簡單介紹一下 IoC/DI
IoC是一個物件導向的設計原則、一種概念,主要作用就是降低類別間的依賴
原本A程式中需要使用C類別 就必須在A中new 一個C的實體
B程式也得用到C類別 就又得多 new 一個C的實體
上圖為例 A跟B對於三個類別都是直接性的依賴,為了避免耦合性過高,我們就得降低A跟B對於各類別的依賴。
IoC 就是A與B 對類別的控制權轉移給第三方
將對於類別的依賴,轉移到依賴第三方的容器
DI 是實踐 IoC 的一種設計模式
透過把程式中依賴的實體,注入到被動接收的物件中
舉個例子:
今晚我想來點胖老爹的美式炸雞桶,以往都是打電話請該店外送,結果今天居然跟我說太忙了人手不足!
上述可知我直接耦合店家的外送員
如果今天變成,我透過Uber Eats 來點餐,我就不需要管店家是否有外送員,而是由Uber Eats幫我安排外送員。
範例可知,我(被動接收的物件)把依賴的對象從店家外送員(被依賴的物件)變成依賴外送平台(DI Container)
private readonly SampleService _service;
public SampleController()
{
_service = new SampleService();
}
上方範例是一般使用實體的方式,都是new一個實體
在 ASP.NET Core 中 我們可以透過 Startup.cs
中的 ConfigureServices()
來註冊應用程式中所需要的服務進DI容器
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<SampleService, SampleService>();
services.AddControllers();
}
上方範例所示,service就是我們的DI容器,透過介面定義所需要的類別,在之後需要做抽換的時候,只要實作同一個介面的類別,就可以進行替換了。
private readonly IService _service;
public SampleController(IService service)
{
_service = service;
}
註冊完之後便可以在建構式中注入所需要的服務。
備註:在 ASP.NET Core 已經有內建許多可注入的服務
Transient (暫時性的實體)
-每次注入時都會建立新的物件
Scoped (具範圍的實體)
-每個 HTTP Request只會共用一個物件,並在第一次注入時建立物件
Singleton (單一實體)
-當應用程式啟動時,會在第一次注入時或在註冊進 DI 容器時建立物件
接著做個範例,來實際看三種生命週期的差異。
首先我們先建立以下檔案
加入內容如下ISample.cs
、IScoped.cs
、ISingleton.cs
、ITransient.cs
public interface ISample
{
}
public interface IScoped : ISample
{
}
public interface ISingleton : ISample
{
}
public interface ITransient : ISample
{
}
SampleService.cs
public class SampleService
{
private readonly ISample _transient;
private readonly ISample _scoped;
private readonly ISample _singleton;
public SampleService(ITransient transient,
IScoped scoped,
ISingleton singleton)
{
_transient = transient;
_scoped = scoped;
_singleton = singleton;
}
public string GetSampleHashCode()
{
return $"Transient: {_transient.GetHashCode()}, "
+ $"Scoped: {_scoped.GetHashCode()}, "
+ $"Singleton: {_singleton.GetHashCode()}";
}
}
接著在Startup
的ConfugureServices()
中註冊各種生命週期的物件
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IScoped, SampleClass>();
services.AddSingleton<ISingleton, SampleClass>();
services.AddTransient<ITransient, SampleClass>();
services.AddTransient<SampleService, SampleService>();
services.AddControllers();
}
並在Controller的建構式注入服務的實體
public class SampleController : ControllerBase
{
private readonly ISample _transient;
private readonly ISample _scoped;
private readonly ISample _singleton;
private readonly SampleService _service;
public SampleController(ITransient transient,
IScoped scoped,
ISingleton singleton,
SampleService service)
{
_transient = transient;
_scoped = scoped;
_singleton = singleton;
_service = service;
}
接著我們新增一個端點,來取得每個物件的HashCode
[HttpGet("")]
public ActionResult<IDictionary<string, string>> Get()
{
var serviceHashCode = _service.GetSampleHashCode();
var controllerHashCode = $"Transient: {_transient.GetHashCode()}, "
+$"Scoped: {_scoped.GetHashCode()}, "
+$"Singleton: {_singleton.GetHashCode()}";
return new Dictionary<string, string> {
{ "Service", serviceHashCode},
{ "Controller", controllerHashCode}
};
}
完成後就可以輸入 dotnet run
運行應用程式,然後透過瀏覽器來觀看結果
可以看到,兩次Request的結果:
Transient 在每次注入的時候都是全新的實體,所以每次取得的HashCode 都會不一樣
Scoped 在一次的Request中都會注入同一個實體,下一個Request則會注入新的實體,所以在同一個Request的HashCode會一致,但是下個Request的HashCode就會不一樣
Singleton的HashCode都是一致的,證明運行期間Singleton的物件都會使用同一個
ASP.NET Core中就自帶DI,所以建議任何服務本身最好也用DI注入其他服務,也不要在服務類別中直接new出其他服務的物件。
參考連結
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1
請問 SampleClass.cs 內容能不能貼上來,不太懂
services.AddScoped<IScoped, SampleClass>();
services.AddSingleton<ISingleton, SampleClass>();
services.AddTransient<ITransient, SampleClass>();
這裡每個都是用 SampleClass,不知道與那三個Interface有何關連。
您好,SampleClass的部分我主要只有建立一個空類別,所以沒有分享內容。
會分三個主要是因為,使用同一個類別,要區分三種不同生命週期做注入,來展示差異。
每個Interface之間沒有關聯,只是要區隔注入項目的內容或週期
不知道這樣有沒有解答到你的疑惑